using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel;
using MacomberMap.Common.Components;
using MacomberMap.Common.Types;
using MacomberMap.Common.User_Interfaces.Components;
using System.Reflection;

namespace MacomberMap.Common.User_Interfaces.Network_Map.Components
{
    /// <summary>
    /// This class holds all the configurable options for a map display, including coordinates and display parameters
    /// </summary>
    public class MM_NetworkMap_Display
    {
        #region Variable declarations
        /// <summary>Our top-left Latitude/Longitude</summary>
        public PointF TopLeft;

        /// <summary>Our top-left Pixel</summary>
        public Point TopLeftXY;

        /// <summary>Our bottom-right Latitude/Longitude</summary>
        public PointF BottomRight;

        /// <summary>Our bottom-right pixel</summary>
        public Point BottomRightXY;

        /// <summary>Our current zoom level</summary>
        public int ZoomLevel;

        /// <summary>
        /// Whether total generation by substation should be shown
        /// </summary>
        [Category("Substations"), Description("Whether total generation by substation should be shown"), DefaultValue(true)]
        public bool ShowTotalGeneration
        {
            get { return _ShowTotalGeneration; }
            set { _ShowTotalGeneration = value; }
        }
        private bool _ShowTotalGeneration;

        /// <summary>
        /// Whether total HSL by substation should be shown
        /// </summary>
        [Category("Substations"), Description("Whether total HSL by substation should be shown"), DefaultValue(true)]
        public bool ShowTotalHSL
        {
            get { return _ShowTotalHSL; }
            set { _ShowTotalHSL = value; }
        }
        private bool _ShowTotalHSL;

        /// <summary>
        /// Whether remaining capacity by substation should be shown
        /// </summary>
        [Category("Substations"), Description("Whether remaining capacity by substation should be shown"), DefaultValue(true)]
        public bool ShowRemainingCapacity
        {
            get { return _ShowRemainingCapacity; }
            set { _ShowRemainingCapacity = value; }
        }
        private bool _ShowRemainingCapacity;

        /// <summary>
        /// Whether total Load by substation should be shown
        /// </summary>
        [Category("Substations"), Description("Whether total Load by substation should be shown"), DefaultValue(true)]
        public bool ShowTotalLoad
        {
            get { return _ShowTotalLoad; }
            set { _ShowTotalLoad = value; }
        }
        private bool _ShowTotalLoad;

        /// <summary>
        /// The types of substations that are shown
        /// </summary>
        [Category("Substations"), Description("The types of substations that are shown"), DefaultValue(MM_NetworkMap_Display.SubstationViewEnum.All)]
        public SubstationViewEnum ShowSubstations
        {
            get { return _ShowSubstations; }
            set { _ShowSubstations = value; }
        }
        private SubstationViewEnum _ShowSubstations = SubstationViewEnum.All;

        /// <summary>
        /// The color for a substation with Load
        /// </summary>
        [Category("Substations"), Description("The color for a substation with Load")]
        public Color LoadColor
        {
            get { return _LoadColor; }
            set { _LoadColor = value; }
        }
        private Color _LoadColor;

        /// <summary>
        /// The color for a substation with Load
        /// </summary>
        [Category("Substations"), Description("The color for a substation without load")]
        public Color StationColor
        {
            get { return _StationColor; }
            set { _StationColor = value; }
        }
        private Color _StationColor;

        
        /// <summary>
        /// Whether substations with units should be drawn as a diamond
        /// </summary>
        [Category("Substations"), Description("Whether substations with units should be drawn as a diamond"), DefaultValue(true)]
        public bool GenerationDiamond
        {
            get { return _GenerationDiamond; }
            set { _GenerationDiamond = value; }
        }
        private bool _GenerationDiamond = true;

        /// <summary>
        /// The size of a substation in pixels
        /// </summary>
        [Category("Substations"), Description("The size of a substation in pixels"), DefaultValue(3f)]
        public float StationSize
        {
            get { return _StationSize; }
            set { _StationSize = value; }
        }
        private float _StationSize = 3f;

        /// <summary>
        /// The zoom level at which substation MW levels are shown
        /// </summary>
        [Category("Zoom"), Description("The zoom level at which substation MW levels are shown")]
        public int StationMWLevel
        {
            get { return _StationMWLevel; }
            set { _StationMWLevel = value; }
        }
        private int _StationMWLevel;

        /// <summary>
        /// The zoom level at which substation Text levels are shown
        /// </summary>
        [Category("Zoom"), Description("The zoom level at which substation Text levels are shown")]
        public int StationNameLevel
        {
            get { return _StationNameLevel; }
            set { _StationNameLevel = value; }
        }
        private int _StationNameLevel;


        /// <summary>
        /// The font for drawing substations
        /// </summary>
        [Category("Substations"), Description("The font for drawing substations")]
        public Font SubstationFont
        {
            get { return _SubstationFont; }
            set { _SubstationFont = value; }
        }
        private Font _SubstationFont = SystemFonts.DialogFont;


        
        /// <summary>
        /// The collection of possible options for viewing the substation
        /// </summary>
        public enum SubstationViewEnum
        {
            /// <summary>Show all substations</summary>
            All,
            /// <summary>Show only substations with units</summary>
            Units,
            /// <summary>Show only substations with loads</summary>
            Loads,
            /// <summary>Show only substations with LAARs</summary>
            LAARs,
            /// <summary>Show only substations with transformers</summary>
            Transformers,
            /// <summary>Show only substations with capacitors</summary>
            Capacitors,
            /// <summary>Show only substations with reactors</summary>
            Reactors,
            /// <summary>Show no substations</summary>
            None
        };
        
        /// <summary>
        /// Whether map tiles should be displayed
        /// </summary>
        [Category("Map"), Description("Whether map tiles should be displayed"), DefaultValue(MM_MapTile.MapType.GoogleMap)]        
        public MM_MapTile.MapType MapTiles
        {
            get { return _MapTiles; }
            set { _MapTiles = value; }
        }
        private MM_MapTile.MapType _MapTiles = MM_MapTile.MapType.GoogleMap;


        /// <summary>
        /// Whether county boundaries should be displayed
        /// </summary>
        [Category("Map"), Description("Whether county boundaries should be displayed"), DefaultValue(true)]
        public bool ShowCounties
        {
            get { return _ShowCounties; }
            set { _ShowCounties = value; }
        }
        private bool _ShowCounties = true;


        /// <summary>
        /// Whether lines should be displayed
        /// </summary>
        [Category("Lines"), Description("Whether lines should be displayed")]
        public Dictionary<MM_KVLevel, bool> ShowLines
        {
            get { return _ShowLines; }
            set
            {
                foreach (KeyValuePair<MM_KVLevel, bool> kvp in value)
                    _ShowLines.Add(kvp.Key, kvp.Value);
            }
        }
        private Dictionary<MM_KVLevel, bool> _ShowLines = new Dictionary<MM_KVLevel, bool>(10);
        #endregion

        #region Event handlers
        /*/// <summary>A substation parameter has changed</summary>        
        public event EventHandler SubstationParameterChanged;*/

        /// <summary>Our coordinates have panned</summary>
        public event EventHandler CoordinatesPan;

        /// <summary>Our zoom level has changed</summary>
        public event EventHandler CoordinatesZoomed;
        #endregion

        #region Static variable constants
        /// <summary>Our highest zoom level</summary>
        public static int MaxZoomLevel = 23;

        /// <summary>Our tile size</summary>
        public static Size TileSize = new Size(256, 256);

        /// <summary>Our collection of constants to improve map rendering</summary>
        private static List<double[]> GConstants;
        #endregion

        #region Initialization
        /// <summary>
        /// Initialize our coordinate space
        /// </summary>
        /// <param name="Top">The top of our space</param>
        /// <param name="Left">The leftmost point of our space</param>
        /// <param name="ZoomLevel">Our zoom level</param>
        public MM_NetworkMap_Display(float Top, float Left, int ZoomLevel)
        {
            
            //Load our constants in place
            GConstants = new List<double[]>(MaxZoomLevel);
            double HalfTileHeight = (double)TileSize.Height;
            for (int a = 0; a <= MaxZoomLevel; a++)
            {
                GConstants.Add(new double[] { HalfTileHeight / 360.0, HalfTileHeight / (2.0 * Math.PI), HalfTileHeight / 2.0, HalfTileHeight });
                HalfTileHeight *= 2;
            }

            //Assign our values
            this.TopLeft = new PointF(Top, Left);
            this.ZoomLevel = ZoomLevel;

            //Now, import everything from the default
            if (MM_NetworkMap.DefaultDisplayOptions != null)
                foreach (PropertyInfo pI in MM_NetworkMap.DefaultDisplayOptions.GetType().GetProperties())
                    if (pI.PropertyType == typeof(Dictionary<MM_KVLevel, bool>))
                    {
                        foreach (KeyValuePair<MM_KVLevel, bool> kvp in pI.GetValue(MM_NetworkMap.DefaultDisplayOptions, null) as Dictionary<MM_KVLevel, bool>)
                            this.ShowLines.Add(kvp.Key, kvp.Value);
                    }
                    else if (pI.CanWrite)
                        this.GetType().GetProperty(pI.Name).SetValue(this, pI.GetValue(MM_NetworkMap.DefaultDisplayOptions, null), null);
        }
        #endregion

        #region Coordinate conversions
        /// <summary>
        /// Convert a latitude/longitude to pixel based on the current zoom level
        /// </summary>
        /// <param name="LatLng">The latitude/longitude</param>
        /// <param name="ZoomLevel">Zoom level</param>
        /// <returns></returns>
        public static Point LatLngToXY(PointF LatLng, int ZoomLevel)
        {
            Point ret = Point.Empty;
            if (ZoomLevel > MaxZoomLevel || ZoomLevel < 1)
                return ret;

            double[] ZoomParams = GConstants[ZoomLevel];
            ret.X = (int)Math.Round(ZoomParams[2] + (LatLng.X * ZoomParams[0]), MidpointRounding.AwayFromZero);

            double f = Math.Min(Math.Max(Math.Sin(LatLng.Y * (Math.PI / 180.0)), -0.9999), 0.9999);
            ret.Y = (int)Math.Round(ZoomParams[2] + (0.5 * Math.Log((1 + f) / (1 - f)) * (-ZoomParams[1])), MidpointRounding.AwayFromZero);

            return ret;
        }


        /// <summary>
        /// Convert a pixel to latitude/longitude based on the current zoom level
        /// </summary>
        /// <param name="XY">The current pixel</param>
        /// <param name="ZoomLevel">The current zoom level</param>
        /// <returns></returns>
        public static PointF XYToLatLng(Point XY, int ZoomLevel)
        {
            PointF OutPoint = PointF.Empty;
            if (ZoomLevel > MaxZoomLevel || ZoomLevel < 1)
                return OutPoint;
            double[] ZoomParams = GConstants[ZoomLevel];
            OutPoint.X = (float)(((double)XY.X - ZoomParams[2]) / ZoomParams[0]);
            OutPoint.Y = (float)((2.0 * Math.Atan(Math.Exp(((double)XY.Y - ZoomParams[2]) / (-ZoomParams[1]))) - (Math.PI / 2.0)) / (Math.PI / 180.0));
            return OutPoint;
        }

        /// <summary>
        /// Convert a pixel to tile coordinate
        /// </summary>
        /// <param name="XY"></param>
        /// <returns></returns>
        public static Point XYToTile(Point XY)
        {
            return new Point(XY.X / TileSize.Width, XY.Y / TileSize.Height);
        }


        /// <summary>
        /// Determine the top-left point of a tile at the requested zoom level
        /// </summary>
        /// <param name="Tile">The tile to be measured</param>
        /// <param name="ZoomLevel">The current zoom level</param>
        /// <returns></returns>
        public static PointF TileTopLeft(Point Tile, int ZoomLevel)
        {
            return XYToLatLng(new Point(Tile.X * TileSize.Width, Tile.Y * TileSize.Height), ZoomLevel);
        }

        /// <summary>
        /// Determine the bottom-right point of a tile at the requested zoom level
        /// </summary>
        /// <param name="Tile">The tile to be measured</param>
        /// <param name="ZoomLevel"></param>
        /// <returns></returns>
        public static PointF TileBottomRight(Point Tile, int ZoomLevel)
        {
            return XYToLatLng(new Point(((Tile.X + 1) * TileSize.Width) - 1, ((Tile.Y + 1) * TileSize.Height) - 1), ZoomLevel);
        }
        #endregion

        #region Tile handling
        /// <summary>
        /// Determine if a tile is valid based on its coordinates
        /// </summary>
        /// <param name="TileCoordinates">The coordinates of the tile</param>
        /// <returns></returns>
        private bool IsValidTile(Point TileCoordinates)
        {
            return TileCoordinates.X / 256 < Math.Pow(2, ZoomLevel) && TileCoordinates.Y / 256 < Math.Pow(2, ZoomLevel);
        }

        /// <summary>
        /// Update our coordinates to reflect a new top/left lat/long
        /// </summary>
        /// <param name="TopLeft">The top-left latitude and longitude</param>
        /// <param name="ParentControl">The control owning the coordinates</param>
        public void UpdateFromLatLng(PointF TopLeft, Control ParentControl)
        {
            //Determine our top-left coordinates
            this.TopLeft = TopLeft;
            this.TopLeftXY = LatLngToXY(TopLeft, ZoomLevel);

            //Determine our bottom-right pixel
            this.BottomRightXY = Point.Add(TopLeftXY, ParentControl.DisplayRectangle.Size);
            this.BottomRight = XYToLatLng(this.BottomRightXY, ZoomLevel);
        }

        /// <summary>
        /// Update our coordinates to reflect a new top-left pixel
        /// </summary>
        /// <param name="TopLeftXY">The top-left pixel</param>
        /// <param name="ParentControl">The control owning the coordinates</param>
        public void UpdateFromXY(Point TopLeftXY, Control ParentControl)
        {
            //Update our top-left coordinates
            this.TopLeftXY = TopLeftXY;
            TopLeft = XYToLatLng(TopLeftXY, ZoomLevel);

            //Determine our bottom-right pixel
            this.BottomRightXY = Point.Add(TopLeftXY, ParentControl.DisplayRectangle.Size);
            this.BottomRight = XYToLatLng(this.BottomRightXY, ZoomLevel);

            if (this.CoordinatesPan != null)
                this.CoordinatesPan(this, EventArgs.Empty);
        }

        /// <summary>
        /// Zoom the coordinates so that our center is maintained at the present zoom level
        /// </summary>
        /// <param name="ZoomLevel">The current zoom level</param>
        /// <param name="ParentControl">The control owning the coordinates</param>
        public void UpdateZoom(int ZoomLevel, Control ParentControl)
        {
            //First, determine the X/Y of our image center
            Point ImageCenter = MM_NetworkMap_Display.LatLngToXY(this.Center, ZoomLevel);

            //Now, extrapolate the rest of our coordinates based on our display rectangle.
            this.TopLeftXY = new Point(ImageCenter.X - (ParentControl.DisplayRectangle.Width / 2), ImageCenter.Y - (ParentControl.DisplayRectangle.Height / 2));
            this.TopLeft = MM_NetworkMap_Display.XYToLatLng(this.TopLeftXY, ZoomLevel);

            this.BottomRightXY = new Point(ImageCenter.X + (ParentControl.DisplayRectangle.Width / 2), ImageCenter.Y + (ParentControl.DisplayRectangle.Height / 2));
            this.BottomRight = MM_NetworkMap_Display.XYToLatLng(this.BottomRightXY, ZoomLevel);
            this.ZoomLevel = ZoomLevel;
            if (this.CoordinatesZoomed != null)
                this.CoordinatesZoomed(this, EventArgs.Empty);


        }
        #endregion

        #region Helper functions
        /// <summary>
        /// Determine the center of our display
        /// </summary>
        public PointF Center
        {
            get { return new PointF((TopLeft.X + BottomRight.X) / 2f, (TopLeft.Y + BottomRight.Y) / 2f); }
        }


        /// <summary>
        /// Update the center of the display
        /// </summary>
        /// <param name="LatLng"></param>
        /// <param name="ParentControl"></param>
        public void SetCenter(PointF LatLng, Control ParentControl)
        {
            Point CenterXY = MM_NetworkMap_Display.LatLngToXY(LatLng, ZoomLevel);
            UpdateFromXY(new Point(CenterXY.X - (ParentControl.DisplayRectangle.Width / 2), CenterXY.Y - (ParentControl.DisplayRectangle.Height / 2)), ParentControl);
        }

        /// <summary>
        /// Determine if a point is visible on our screen
        /// </summary>
        /// <param name="Substation">The substation to check</param>
        /// <returns></returns>
        public bool IsVisible(MM_Substation Substation)
        {

            if (float.IsNaN(Substation.LatLong.X))
                return false;
            else if (Substation.LatLong.X < TopLeft.X || Substation.LatLong.X > BottomRight.X || Substation.LatLong.Y > TopLeft.Y || Substation.LatLong.Y < BottomRight.Y)
                return false;
            else if (ShowSubstations == SubstationViewEnum.All)
                return true;
            else if (ShowSubstations == SubstationViewEnum.None)
                return false;
            else if (ShowSubstations == SubstationViewEnum.Capacitors && Substation.ElemTypes.Contains(MM_Overall_Display.ElemTypes["Capacitor"]))
                return true;
            else if (ShowSubstations == SubstationViewEnum.Reactors && Substation.ElemTypes.Contains(MM_Overall_Display.ElemTypes["Reactor"]))
                return true;
            else if (ShowSubstations == SubstationViewEnum.LAARs && Substation.ElemTypes.Contains(MM_Overall_Display.ElemTypes["LAAR"]))
                return true;
            else if (ShowSubstations == SubstationViewEnum.Loads && Substation.ElemTypes.Contains(MM_Overall_Display.ElemTypes["Load"]))
                return true;
            else if (ShowSubstations == SubstationViewEnum.Transformers && Substation.ElemTypes.Contains(MM_Overall_Display.ElemTypes["Transformer"]))
                return true;
            else if (ShowSubstations == SubstationViewEnum.Units && Substation.ElemTypes.Contains(MM_Overall_Display.ElemTypes["Unit"]))
                return true;
            else
                return false;
        }

        /// <summary>
        /// Determine if a line is visible on our screen
        /// </summary>
        /// <param name="Line">The line to check</param>
        /// <returns></returns>
        public bool IsVisible(MM_Line Line)
        {
            bool ShowLine = false;
            if (!_ShowLines.TryGetValue(Line.KVLevel, out ShowLine))
                ShowLines.Add(Line.KVLevel, ShowLine = true);
            if (!ShowLine)
                return false;
            else if ((Line.Substation1.LatLong.X < TopLeft.X) && (Line.Substation2.LatLong.X < TopLeft.X))
                return false;
            else if ((Line.Substation1.LatLong.Y > TopLeft.Y) && (Line.Substation2.LatLong.Y > TopLeft.Y))
                return false;
            else if ((Line.Substation1.LatLong.X > BottomRight.X) && (Line.Substation2.LatLong.X > BottomRight.X))
                return false;
            else if ((Line.Substation1.LatLong.Y < BottomRight.Y) && (Line.Substation2.LatLong.Y < BottomRight.Y))
                return false;
            return true;

        }

        /// <summary>
        /// Determine whether the boundary is visible
        /// </summary>
        /// <param name="Boundary">The boundary to check against</param>
        /// <returns>Whether or not the boundary could possibly be visible on the screen</returns>
        public bool IsVisible(MM_Boundary Boundary)
        {
            if ((BottomRight.X < Boundary.Min.X) || (TopLeft.Y < Boundary.Min.Y))
                return false;
            else if ((TopLeft.X > Boundary.Max.X) || (BottomRight.Y > Boundary.Max.Y))
                return false;
            return true;

        }
        #endregion

        /// <summary>
        /// Convert a latitude/longitude to screen point
        /// </summary>
        /// <param name="inPoint">The point to convert</param>
        /// <returns></returns>
        public Point ConvertPoint(PointF inPoint)
        {
            Point XYPoint = MM_NetworkMap_Display.LatLngToXY(inPoint, this.ZoomLevel);
            return new Point(XYPoint.X - TopLeftXY.X, XYPoint.Y - TopLeftXY.Y);
        }
    }
}
